package net.apexes.commons.net;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author hedyn
 */
public class MulticastMonitor {

    private final CopyOnWriteArraySet<Listener> listeners = new CopyOnWriteArraySet<>();
    private final InetAddress group;
    private final int port;
    private final int maxMessageSize;
    private final MulticastSocket socket;
    private boolean closed = false;

    public MulticastMonitor(String host, int port) throws IOException {
        this(host, port, 4096);
    }

    public MulticastMonitor(String host, int port, int maxMessageSize) throws IOException {
        this.group = InetAddress.getByName(host);
        this.port = port;
        this.maxMessageSize = maxMessageSize;
        this.socket = new MulticastSocket(port);
        this.socket.joinGroup(group);
    }

    public void listen() throws IOException {
        try {
            while (!closed) {
                byte[] buffer = new byte[maxMessageSize];
                DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
                socket.receive(datagramPacket);
                byte[] messageBytes;
                if (datagramPacket.getLength() < buffer.length) {
                    messageBytes = new byte[datagramPacket.getLength()];
                    System.arraycopy(buffer, 0, messageBytes, 0, messageBytes.length);
                } else {
                    messageBytes = buffer;
                }
                for (Listener listener : listeners) {
                    try {
                        listener.onMessage(messageBytes);
                    } catch (Exception ignored) {
                    }
                }
            }
        } catch (Exception e) {
            if (!closed) {
                throw e;
            }
        }
    }

    public synchronized void close() throws IOException {
        if (closed) {
            return;
        }
        closed = true;
        socket.leaveGroup(group);
        socket.close();
    }

    public boolean isClosed() {
        return closed;
    }

    public void sendMessage(byte[] messageBytes) throws IOException {
        socket.send(new DatagramPacket(messageBytes, messageBytes.length, group, port));
    }

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    public interface Listener {
        void onMessage(byte[] messageBytes);
    }

}
